//
// WinPcap.net
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using System;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;

namespace WinPcap
{
	/// <summary>
	/// Represents the method that will handle the 
	/// <see cref="NetworkInterface.PacketReceived"/> event.
	/// </summary>
	/// <param name="sender">The event source.</param>
	/// <param name="e">A <see cref="PacketReceivedEventArgs"/> containing the
	/// <see cref="Packet"/> received.</param>
	public delegate void PacketReceivedEventHandler( object sender, PacketReceivedEventArgs e );

	/// <summary>
	/// A network interface.
	/// </summary>
	public sealed class NetworkInterface : IDisposable
	{
		/// <summary>
		/// The name of the network interface device.
		/// </summary>
		string m_Name = String.Empty;

		/// <summary>
		/// The human readable network interface description.
		/// </summary>
		string m_Description = String.Empty;

		/// <summary>
		/// Status flag indicating whether the network interface is
		/// the loopback device.
		/// </summary>
		bool m_IsLoopback;
		
		/// <summary>
		/// The packet filter.
		/// </summary>
		string m_PacketFilter = String.Empty;
		
		/// <summary>
		/// The open network interface.
		/// </summary>
		IntPtr m_OpenNetworkInterface = IntPtr.Zero;
		
		/// <summary>
		/// The network addresses associated with this network interface.
		/// </summary>
		NetworkInterfaceAddressCollection m_NetworkAddresses = 
			new NetworkInterfaceAddressCollection( );
		
		/// <summary>
		/// Main processing thread.
		/// </summary>
		Thread m_ProcessingThread;

		/// <summary>
		/// Status flag indicating whether the processing thread
		/// should keep running and processing packets.
		/// </summary>
		bool m_IsRunning;
		
		/// <summary>
		/// The data link layer of this network interface.
		/// </summary>
		DataLinkLayerType m_DataLinkLayerType = DataLinkLayerType.Unknown;
		
		/// <summary>
		/// Indicates whether this class has been disposed.  A count of 
		/// greater than 0 means it has been disposed..
		/// </summary>
		int m_Disposed;
		
		/// <summary>
		/// Occurs when a packet received by this network interface.
		/// </summary>
		public event PacketReceivedEventHandler PacketReceived;

		/// <summary>
		/// Creates a new instance of the <see cref="NetworkInterface"/> class.
		/// </summary>
		public NetworkInterface( )
		{
		}
		
		/// <summary>
		/// Releases all resources used by the <see cref="NetworkInterface"/>.
		/// </summary>
		~NetworkInterface( )
		{
			Dispose( false );
		}
		
		/// <summary>
		/// Disposes this class.
		/// </summary>
		public void Dispose( )
		{
			Dispose( true );
			GC.SuppressFinalize( this );
		}
		
		/// <summary>
		/// Gets or sets the user friendly network interface description.
		/// </summary>
		public string Description
		{
			get
			{
				return m_Description;
			}
			
			set
			{
				if( value != null )
				{
					m_Description = value;
				}
				else
				{
					m_Description = String.Empty;
				}
			}
		}
		
		/// <summary>
		/// Gets or sets the name of the network interface.
		/// </summary>
		public string Name
		{
			get
			{
				return m_Name;
			}
			
			set
			{
				m_Name = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the flag indicating whether the network interface
		/// is the local loopback device.
		/// </summary>
		public bool IsLoopback
		{
			get
			{
				return m_IsLoopback;
			}
			
			set
			{
				m_IsLoopback = value;
			}
		}

		/// <summary>
		/// Gets or sets the packet filter.
		/// </summary>
		public string PacketFilter
		{
			get
			{
				return m_PacketFilter;
			}
			
			set
			{
				m_PacketFilter = value;
			}
		}
		
		/// <summary>
		/// Gets the set of network addresses associated with
		/// this network interface.
		/// </summary>
		public NetworkInterfaceAddressCollection Addresses
		{
			get
			{
				return m_NetworkAddresses;
			}
		}
		
		/// <summary>
		/// Gets all the network interfaces for the local machine.
		/// </summary>
		/// <returns>A collection of network interfaces.</returns>
		/// <exception cref="DllNotFoundException">WinPcap is not
		/// installed on this machine.</exception>
		/// <exception cref="WinPcapException">An error occurred
		/// whilst trying to read the network interfaces on this
		/// machine.</exception>
		public static NetworkInterfaceCollection GetAllNetworkInterfaces( )
		{		
			NetworkInterfaceCollection DevicesCollection = new NetworkInterfaceCollection( );

			// Read devices from pcap library.

			IntPtr PcapDevices = NativeMethods.pcap_findalldevs( );

			// Copy the pcap device information into a .Net compatible device object.

			IntPtr DeviceInfoPtr = PcapDevices;
			
			while( DeviceInfoPtr != IntPtr.Zero )
			{
				NativeMethods.pcap_if Info  = ( NativeMethods.pcap_if )Marshal.PtrToStructure( DeviceInfoPtr, typeof( NativeMethods.pcap_if ) );
				NetworkInterface networkInterface = Info.ToNetworkInterface( );	
				DevicesCollection.Add( networkInterface );
				
				DeviceInfoPtr = Info.next;
			}
  
			// Free the device list
			if( PcapDevices != IntPtr.Zero )
			{
				NativeMethods.pcap_freealldevs( PcapDevices );
			}
			
			return DevicesCollection;
		}
		
		/// <summary>
		/// Closes the network interface.
		/// </summary>
		public void Close( )
		{
			Dispose( );
		}
		
		/// <summary>
		/// Opens a network interface retrieved from 
		/// <see cref="GetAllNetworkInterfaces"/> for packet capture.
		/// </summary>
		/// <exception cref="ObjectDisposedException">
		/// The <see cref="NetworkInterface"/> has been closed.
		/// </exception>
		/// <exception cref="WinPcapException">An error occurred
		/// whilst trying to open the network interface.</exception>
		public void Open( )
		{
			if( IsDisposed )
			{
				throw new ObjectDisposedException( GetType( ).FullName );
			}
			
			// Open the network interface.
			
			string Source = NativeMethods.pcap_createsrcstr( NativeMethods.SourceTypes.LocalInterface, m_Name );
			m_OpenNetworkInterface = NativeMethods.pcap_open( Source );
	
			// Get the netmask to use for the packet filter.
			
			IPEndPoint EndPoint = null;
				
			if( m_NetworkAddresses.Count > 0 )
			{
				EndPoint = m_NetworkAddresses[ 0 ].NetMask;
			}
			
			if( EndPoint == null )
			{
				// Assume we are in a C class network.
				
				EndPoint = new IPEndPoint( 0xFFFFFF, 0 );
			}
			
			// Set the packet filter.
	
			byte[ ] AddressBytes = EndPoint.Address.GetAddressBytes( );
			int Address = GetIPAddressAsInt32( AddressBytes );
			NativeMethods.SetPacketFilter( m_OpenNetworkInterface, m_PacketFilter, 0, Address );

			// Read the data link type.
	
			m_DataLinkLayerType = NativeMethods.pcap_datalink( m_OpenNetworkInterface );
	
			// Start processing packets.
	
			StartProcessingPackets( );
			
			// Ensure this class is not garbage collected until this point.
			// This is probably not necessary since the thread started will
			// keep this object alive.
			
			GC.KeepAlive( this );
		}
		
		/// <summary>
		/// Opens a network interface with the specified device name.
		/// </summary>
		/// <param name="name">The device name.</param>
		/// <exception cref="DllNotFoundException">WinPcap is not
		/// installed on this machine.</exception>
		/// <exception cref="WinPcapException">An error occurred
		/// whilst trying to read the network interfaces on this
		/// machine or attempting to open the network interface.</exception>
		public void Open( string name )
		{
			// Find matching network interface.
		
			NetworkInterface MatchingNetworkInterface = FindNetworkInterface( name );
			
			if( MatchingNetworkInterface != null )
			{
				Clone( MatchingNetworkInterface );
				Open( );
			}
			else
			{
				// No corresponding network interface.
				
				throw new WinPcapException( String.Concat( "No network interface found with name ", name, "." ) );
			}
		}
		
		/// <summary>
		/// Finds a network interface with a specified name from the
		/// set of network interfaces on the local machine.
		/// </summary>
		/// <param name="name">The name of the network
		/// interface.</param>
		/// <exception cref="DllNotFoundException">WinPcap is not
		/// installed on this machine.</exception>
		/// <exception cref="WinPcapException">An error occurred
		/// whilst trying to read the network interfaces on this
		/// machine.</exception>
		public static NetworkInterface FindNetworkInterface( string name )
		{
			NetworkInterface MatchingNetworkInterface = null;
			
			// Try to find a local machine network interface that
			// has the same device name.
			
			NetworkInterfaceCollection AllNetworkInterfaces = 
				GetAllNetworkInterfaces( );
			
			foreach( NetworkInterface NetworkInterface in AllNetworkInterfaces )
			{
				if( String.Compare( NetworkInterface.m_Name, name ) == 0 )
				{
					// We have a match.
					MatchingNetworkInterface = NetworkInterface;
					break;
				}
			}
			
			return MatchingNetworkInterface;
		}
		
		/// <summary>
		/// This overrides Object.Equals.
		/// </summary>
		/// <param name="value">An object to test for equality.</param>
		/// <returns>A flag indicating whether the two classes are
		/// equal.</returns>
		public override bool Equals( object value )
		{
			bool Equals = false;
			
			NetworkInterface NetworkInterfaceToCompare = value as NetworkInterface;
			
			if( NetworkInterfaceToCompare != null )
			{
				// Check the description.
		
				Equals = NetworkInterfaceToCompare.Description.Equals( Description );
		
				// Check the name.
		
				if( Equals )
				{
					Equals = NetworkInterfaceToCompare.Name.Equals( Name );
				}
		
				// Check the loopback flag.
		
				if( Equals )
				{
					if( NetworkInterfaceToCompare.IsLoopback != IsLoopback )
					{
						Equals = false;
					}
				}
		
				// Check the data link layer.
		
				if( Equals )
				{
					if( NetworkInterfaceToCompare.DataLinkLayerType != DataLinkLayerType )
					{
						Equals = false;
					}
				}
			}
			
			return Equals;
		}
		
		/// <summary>
		/// Gets the hash code of this <see cref="NetworkInterface"/>.
		/// </summary>
		/// <returns>The has code of this <see cref="NetworkInterface"/>.</returns>
		public override int GetHashCode( )
		{
			return m_Description.GetHashCode( ) ^ m_Name.GetHashCode( ) ^ m_IsLoopback.GetHashCode( ) ^ m_DataLinkLayerType.GetHashCode( );
		}

		/// <summary>
		/// Gets or sets the data link layer type.
		/// </summary>
		public DataLinkLayerType DataLinkLayerType
		{
			get
			{
				return m_DataLinkLayerType;
			}
			
			set
			{
				m_DataLinkLayerType = value;
			}
		}
		
		/// <summary>
		/// Stops the data packet processing.
		/// </summary>
		void StopProcessingPackets( )
		{	
			if( m_ProcessingThread != null )
			{
				m_IsRunning = false;
				m_ProcessingThread.Join( );
				m_ProcessingThread = null;
			}
		}
		
		/// <summary>
		/// Starts processing data packets.
		/// </summary>
		/// <remarks>
		/// This starts up an internal thread which reads data packets sent to this 
		/// network interface and raises events.
		/// </remarks>
		void StartProcessingPackets( )
		{	
			m_ProcessingThread = new Thread( new ThreadStart( ProcessDataPackets ) );

			m_IsRunning = true;
			m_ProcessingThread.Start( );
		}
		
		/// <summary>
		/// Processes data packets received by the network interface.
		/// </summary>
		void ProcessDataPackets( )
		{
			while( m_IsRunning )
			{
				try
				{
					Packet DataPacket = GetNextPacket( );
	
					if( DataPacket != null )
					{
						// Data received.
						
						PacketReceivedEventArgs Args = new PacketReceivedEventArgs( DataPacket );
						OnPacketReceived( Args );
					}
					else 
					{
						// Timed out.
					}
				}
				catch( WinPcapException Ex )
				{
					// Report error.
	
					Debug.WriteLine( Ex.Message );
					Thread.Sleep( 50 );
				}
			}
		}
		
		/// <summary>
		/// Raises the <see cref="PacketReceived"/> event.
		/// </summary>
		void OnPacketReceived( PacketReceivedEventArgs e )
		{
			if( PacketReceived != null )
			{
				PacketReceived( this, e );
			}
		}
		
		/// <summary>
		/// Converts a byte array representing an IP address into
		/// an int.
		/// </summary>
		/// <param name="bytes">A byte array representing an IP address.</param>
		/// <returns>An IP address as a int.</returns>
		static int GetIPAddressAsInt32( byte[ ] bytes )
		{
			long Address = BitConverter.ToInt32( bytes, 0 ) & 0xFFFFFFFF;
			return Convert.ToInt32( Address );
		}
		
		/// <summary>
		/// Gets the next data packet from the network interface.
		/// </summary>
		/// <remarks>No need to call <see cref="GC.KeepAlive"/> in this 
		/// method since this is only called by the thread created by this
		/// class and is not called by any publicly visible methods.</remarks>
		/// <returns>A <see cref="Packet"/> read from the network interface.</returns>
		Packet GetNextPacket( )
		{
			Packet PacketReceived = null;
	
			IntPtr HeaderPtr = IntPtr.Zero;
			IntPtr DataPtr = IntPtr.Zero;
			
			int Status = NativeMethods.pcap_next_ex( m_OpenNetworkInterface, out HeaderPtr, out DataPtr );
	
			if( Status == 1 )
			{
				// Data arrived.
				
				if( HeaderPtr != IntPtr.Zero )
				{
					PacketReceived = CreatePacket( HeaderPtr, DataPtr );
				}
			}
			else if( Status == 0 )
			{
				// Timed out.
			}
			else if( Status == -1 )
			{
				// Error.
				
				string ErrorText = NativeMethods.pcap_geterr( m_OpenNetworkInterface, "GetNextPacket error." );
				throw new WinPcapException( ErrorText );
			}

			return PacketReceived;
		}
		
		/// <summary>
		/// Creates a <see cref="Packet"/> object from the WinPcap header and 
		/// data payload.
		/// </summary>
		/// <param name="headerPtr">A pointer to a <see cref="NativeMethods.pcap_pkthdr"/>
		/// struct.</param>
		/// <param name="dataPtr">A pointer to an array of bytes containing
		/// the packet data.</param>
		/// <returns>A <see cref="Packet"/>.</returns>
		static Packet CreatePacket( IntPtr headerPtr, IntPtr dataPtr )
		{
			// Copy packet data into a byte array.
	
			NativeMethods.pcap_pkthdr Header = ( NativeMethods.pcap_pkthdr )Marshal.PtrToStructure( headerPtr, typeof( NativeMethods.pcap_pkthdr ) );
			int PacketLength = ( int )Header.caplen;
			byte[ ] Bytes = new byte[ PacketLength ];
			
			if( dataPtr != IntPtr.Zero )
			{
				Marshal.Copy( dataPtr, Bytes, 0, PacketLength );
			}
			
			// Create new packet.
	
			Packet DataPacket = new Packet( Bytes );
			return DataPacket;
		}
		
		/// <summary>
		/// Clones the network interface.
		/// </summary>
		/// <param name="networkInterface">The network interface
		/// to clone.</param>
		void Clone( NetworkInterface networkInterface )
		{
			m_Name = networkInterface.m_Name;
			m_Description = networkInterface.m_Description;
			m_NetworkAddresses = networkInterface.m_NetworkAddresses;
			m_IsLoopback = networkInterface.m_IsLoopback;
		}
		
		/// <summary>
		/// Disposes this <see cref="NetworkInterface"/> class.
		/// </summary>
		/// <param name="disposing"><see langword="true"/> if the
		/// method should dispose managed and unmanaged objects; 
		/// <see langword="false"/> if only unmanaged objects 
		/// should be cleaned up.</param>
		void Dispose( bool disposing )
		{
			if( Interlocked.Increment( ref m_Disposed ) == 1 )
			{
				if( disposing )
				{
					// Stop processing packets.
			
					StopProcessingPackets( );
				}
		
				// Close the network interface.
		
				if( m_OpenNetworkInterface != IntPtr.Zero )
				{
					NativeMethods.pcap_close( m_OpenNetworkInterface );
					m_OpenNetworkInterface = IntPtr.Zero;
				}
						
				// Ensure we are not finalized until this point. 
				GC.KeepAlive( this );
			}
		}
		
		/// <summary>
		/// Returns whether this object has been disposed.
		/// </summary>
		bool IsDisposed
		{
			get
			{
				return m_Disposed > 0;
			}
		}
	}
}
